package com.hundsun.cprs.yyt.common.redis;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;

/**
 * 基于redis的原子计数器
 * 
 * @author linrl
 * @date 2018年4月20日
 */
@Component
public class RedisCounterFactory {
    @Autowired
    private RedisTemplate<String, Serializable> redisTemplate;
    
    private static final String CIRCLE_SEQUENCE_SCRIPT;
    
    static{
        StringBuilder sb = new StringBuilder();
        sb.append("local expireTime = ARGV[2] ");
        sb.append("local result = redis.call(\"incr\",KEYS[1]) ");
        sb.append("if result == tonumber(ARGV[1]) ");
        sb.append("then ");
        sb.append("  redis.call(\"set\",KEYS[1],0) ");
        sb.append("end ");
        sb.append("if expireTime ~= nil ");
        sb.append("then ");
        sb.append("  redis.call(\"expire\",KEYS[1],expireTime)");
        sb.append("end ");
        sb.append("return result");
        CIRCLE_SEQUENCE_SCRIPT = sb.toString();
    }
    /**
     * @Title: set
     * @Description: set cache.
     * @param key
     * @param value
     * @param expireTime
     */
    public void set(String key, int value, Date expireTime) {
        RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        counter.set(value);
        counter.expireAt(expireTime);
    }

    /**
     * @Title: set
     * @Description: set cache.
     * @param key
     * @param value
     * @param timeout
     * @param unit
     */
    public void set(String key, int value, long timeout, TimeUnit unit) {
        RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        counter.set(value);
        counter.expire(timeout, unit);
    }
    
    
    /**
     * 检查指定KEY是否存在
     * @param key
     * @return
     */
    public boolean exist(String key){
        return redisTemplate.hasKey(key);
    }
    
    public long get(String key){
        RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        return counter.get();
    }
    
    /**
     * 循环序列生产器,默认最大序列号999,到达最大序列则清理0，从1重新计数
     * @param key
     * @return
     */
    public long circleSequence(String key){
        return circleSequence(key,999);
    }
    
    public long circleSequence(String key,int maxSequence){
        return circleSequence(key,maxSequence,-1);
    }
    
    public long circleSequence(String key,int maxSequence,long expire){
        final List<String> keys = new ArrayList<String>();
        keys.add(key);
        final List<String> args = new ArrayList<String>();
        args.add(maxSequence + "");
        if (expire > 0) {
            args.add(expire + "");
        }
        
        Long result = redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                Object nativeConnection = connection.getNativeConnection();
                if (nativeConnection instanceof JedisCluster) {
                    return (Long)((JedisCluster)nativeConnection).eval(CIRCLE_SEQUENCE_SCRIPT, keys, args);
                }
                // 单机模式
                else if (nativeConnection instanceof Jedis) {
                    return (Long)((Jedis)nativeConnection).eval(CIRCLE_SEQUENCE_SCRIPT, keys, args);
                }
                return null;
            }
        });
        return result;
    }

    /**
     * @Title: generate
     * @Description: Atomically increments by one the current value.
     * @param key
     * @return
     */
    public long generate(String key) {
        RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        return counter.incrementAndGet();
    }

    /**
     * @Title: generate
     * @Description: Atomically increments by one the current value.
     * @param key
     * @return
     */
    public long generate(String key, Date expireTime) {
        RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        counter.expireAt(expireTime);
        return counter.incrementAndGet();
    }
    
    public long generate(String key, long timeout, TimeUnit unit) {
        RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        counter.expire(timeout, unit);
        return counter.incrementAndGet();
    }
    
    public boolean expire(String key, long timeout, TimeUnit unit){
        RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        return counter.expire(timeout, unit);
    }

    /**
     * @Title: generate
     * @Description: Atomically adds the given value to the current value.
     * @param key
     * @param increment
     * @return
     */
    public long generate(String key, int increment) {
        RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        return counter.addAndGet(increment);
    }

    /**
     * @Title: generate
     * @Description: Atomically adds the given value to the current value.
     * @param key
     * @param increment
     * @param expireTime
     * @return
     */
    public long generate(String key, int increment, Date expireTime) {
        RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        counter.expireAt(expireTime);
        return counter.addAndGet(increment);
    }
    
    public long decrementAndGet(String key,Date expireTime){
        RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        counter.expireAt(expireTime);
        return counter.decrementAndGet();
    }
    
    public long decrementAndGet(String key,long timeout, TimeUnit unit){
        RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        counter.expire(timeout, unit);
        return counter.decrementAndGet();
    }
}
